import IPython.display as ipd
from IPython.display import Image
from matplotlib import pyplot as plt
from matplotlib import patches
from matplotlib import colors
import pretty_midi
import pandas as pd
from utils.plot_tools import color_argument_to_dict
2.2. 기호 표현
Symbolic Representation
음악의 표현 방법 중 기호(심볼릭) 표현에 대해 알아본다. 피아노-롤(piano-roll), 미디(MIDI) 등이 있다.
이 글은 FMP(Fundamentals of Music Processing) Notebooks을 참고로 합니다.
기호/심볼릭(symbolic) 표현
- 기호(symbolic) 음악 표현이란 음표나 여타 음악 이벤트들을 명시적으로 인코딩한 모든 종류의 악보 표현을 의미한다.
피아노-롤(piano-roll) 표현
피아노-롤은 피아노와 관련된 음 정보들을 모아 가시화한 것을 일반적으로 말한다.
드뷔시와 베토벤 음악의 피아노롤을 아래 영상과 같이 표현할 수 있다.
"LlvUepMa31o", start=15) ) ipd.display( ipd.YouTubeVideo(
"Kri2jWr08S4", start=11) ) ipd.display( ipd.YouTubeVideo(
미디 (MIDI) 표현
또다른 기호 표현으로는 MIDI(Musical Instrument Digital Interface) 스탠다드가 있다. MIDI는 1980년대 초반 전자 음악 악기 시장의 급성장과 함께 출현했다.
MIDI 메시지는 음(note) 온셋, 음 오프셋, 강도(intensity or “velocity”)와 같은 정보를 인코딩한다. 컴퓨터에서 MIDI 파일은 MIDI 메시지들과 다른 메타데이터를 보관한다.
MIDI 노트넘버(MIDI note number)는 0과 127 사이의 정수로 노트의 피치를 인코딩한다. 가장 중요한 것으로는 C4(중간 C)는 MIDI 노트넘버 60이고, A4(concert A440)은 MIDI 노트넘버 69이다. MIDI 노트넘버는 12개로 나누어져있으며 한 옥타브씩 나누어진다 (e.g. 72 = C5, 84 = C6, etc.)
"../img/2.music_representation/FMP_C1_MIDI-NoteNumbers.png", width=500) Image(
키 벨로시티(key velocity)는 0과 127 사이의 정수로 소리의 강도를 조정한다.
MIDI 채널은 0과 15 사이의 정수로 신디사이저가 특정 악기를 사용하도록 안내한다.
MIDI는 사분음표를 clock pulses 또는 틱으로 세분화한다. 예를 들어, 분기 음 당 펄스 수(PPQN)를 120으로 정의하면 60개의 틱이 8번째 음의 길이를 나타낸다.
또한 MIDI는 템포를 BPM으로 인코딩하여 절대적인 시간 정보를 알려준다.
"../img/2.music_representation/FMP_C1_F13.png", width=600) Image(
= pretty_midi.PrettyMIDI("../data_FMP/FMP_C1_F01_Beethoven_FateMotive_Sibelius-Tracks.mid")
midi_data = []
midi_list
for instrument in midi_data.instruments:
for note in instrument.notes:
= note.start
start = note.end
end = note.pitch
pitch = note.velocity
velocity
midi_list.append([start, end, pitch, velocity, instrument.name])
= sorted(midi_list, key=lambda x: (x[0], x[2]))
midi_list
= pd.DataFrame(midi_list, columns=['Start', 'End', 'Pitch', 'Velocity', 'Instrument'])
df = df.to_html(index=False)
html ipd.HTML(html)
Start | End | Pitch | Velocity | Instrument |
---|---|---|---|---|
0.25 | 0.50 | 43 | 113 | Piano |
0.25 | 0.50 | 55 | 76 | Piano |
0.25 | 0.50 | 67 | 76 | Piano |
0.50 | 0.75 | 43 | 113 | Piano |
0.50 | 0.75 | 55 | 76 | Piano |
0.50 | 0.75 | 67 | 76 | Piano |
0.75 | 1.00 | 43 | 113 | Piano |
0.75 | 1.00 | 55 | 76 | Piano |
0.75 | 1.00 | 67 | 76 | Piano |
1.00 | 2.00 | 39 | 126 | Piano |
1.00 | 2.00 | 51 | 126 | Piano |
1.00 | 2.00 | 63 | 70 | Piano |
2.25 | 2.50 | 41 | 113 | Piano |
2.25 | 2.50 | 53 | 76 | Piano |
2.25 | 2.50 | 65 | 76 | Piano |
2.50 | 2.75 | 41 | 113 | Piano |
2.50 | 2.75 | 53 | 76 | Piano |
2.50 | 2.75 | 65 | 76 | Piano |
2.75 | 3.00 | 41 | 113 | Piano |
2.75 | 3.00 | 53 | 76 | Piano |
2.75 | 3.00 | 65 | 76 | Piano |
3.00 | 5.00 | 38 | 112 | Piano |
3.00 | 5.00 | 50 | 126 | Piano |
3.00 | 5.00 | 62 | 71 | Piano |
= 22050
Fs = midi_data.synthesize(fs=Fs)
audio_data =Fs) ipd.Audio(audio_data, rate
def midi_to_list(midi):
"""Convert a midi file to a list of note events
Args:
midi (str or pretty_midi.pretty_midi.PrettyMIDI): Either a path to a midi file or PrettyMIDI object
Returns:
score (list): A list of note events where each note is specified as
``[start, duration, pitch, velocity, label]``
"""
if isinstance(midi, str):
= pretty_midi.pretty_midi.PrettyMIDI(midi)
midi_data elif isinstance(midi, pretty_midi.pretty_midi.PrettyMIDI):
= midi
midi_data else:
raise RuntimeError('midi must be a path to a midi file or pretty_midi.PrettyMIDI')
= []
score
for instrument in midi_data.instruments:
for note in instrument.notes:
= note.start
start = note.end - start
duration = note.pitch
pitch = note.velocity / 128.
velocity
score.append([start, duration, pitch, velocity, instrument.name])return score
def visualize_piano_roll(score, xlabel='Time (seconds)', ylabel='Pitch', colors='FMP_1', velocity_alpha=False,
=(12, 4), ax=None, dpi=72):
figsize"""Plot a pianoroll visualization
Args:
score: List of note events
xlabel: Label for x axis (Default value = 'Time (seconds)')
ylabel: Label for y axis (Default value = 'Pitch')
colors: Several options: 1. string of FMP_COLORMAPS, 2. string of matplotlib colormap,
3. list or np.ndarray of matplotlib color specifications,
4. dict that assigns labels to colors (Default value = 'FMP_1')
velocity_alpha: Use the velocity value for the alpha value of the corresponding rectangle
(Default value = False)
figsize: Width, height in inches (Default value = (12)
ax: The Axes instance to plot on (Default value = None)
dpi: Dots per inch (Default value = 72)
Returns:
fig: The created matplotlib figure or None if ax was given.
ax: The used axes
"""
= None
fig if ax is None:
= plt.figure(figsize=figsize, dpi=dpi)
fig = plt.subplot(1, 1, 1)
ax
= sorted(set([note[4] for note in score]))
labels_set = color_argument_to_dict(colors, labels_set)
colors
= min(note[2] for note in score)
pitch_min = max(note[2] for note in score)
pitch_max = min(note[0] for note in score)
time_min = max(note[0] + note[1] for note in score)
time_max
for start, duration, pitch, velocity, label in score:
if velocity_alpha is False:
= None
velocity = patches.Rectangle((start, pitch - 0.5), duration, 1, linewidth=1,
rect ='k', facecolor=colors[label], alpha=velocity)
edgecolor
ax.add_patch(rect)
- 1.5, pitch_max + 1.5])
ax.set_ylim([pitch_min min(time_min, 0), time_max + 0.5])
ax.set_xlim([
ax.set_xlabel(xlabel)
ax.set_ylabel(ylabel)
ax.grid()True)
ax.set_axisbelow(=1, edgecolor='k', facecolor=colors[key]) for key in labels_set],
ax.legend([patches.Patch(linewidth='upper right', framealpha=1)
labels_set, loc
if fig is not None:
plt.tight_layout()
return fig,
= midi_to_list(midi_data)
score =(8, 3), velocity_alpha=True); visualize_piano_roll(score, figsize
= pretty_midi.PrettyMIDI("../data_FMP/FMP_C1_F12_Bach_BWV846_Sibelius-Tracks.mid")
midi_data = midi_to_list(midi_data)
score =(8, 3), velocity_alpha=True); visualize_piano_roll(score, figsize
import music21 as m21
= m21.converter.parse("../data_FMP/FMP_C1_F12_Bach_BWV846_Sibelius-Tracks.mid")
s 'pianoroll', figureSize=(10, 3)) s.plot(
악보적(score) 표현
기호적 악보 표현은 “2.1.Sheet_Music.ipynb”에서 설명한 음악적 기호들을 인코딩한다. (음자리표, 조표 등등) 하지만 이를 악보로 가시화하는 것이 아니라 저장하는데, MusicXML같은 파일로 저장한다.
아래 그 예시가 있다.
"../img/2.music_representation/FMP_C1_F15.png", width=400) Image(
def xml_to_list(xml):
"""Convert a music xml file to a list of note events
Args:
xml (str or music21.stream.Score): Either a path to a music xml file or a music21.stream.Score
Returns:
score (list): A list of note events where each note is specified as
``[start, duration, pitch, velocity, label]``
"""
if isinstance(xml, str):
= m21.converter.parse(xml)
xml_data elif isinstance(xml, m21.stream.Score):
= xml
xml_data else:
raise RuntimeError('midi must be a path to a midi file or music21.stream.Score')
= []
score
for part in xml_data.parts:
= part.getInstrument().instrumentName
instrument
for note in part.flat.notes:
if note.isChord:
= note.offset
start = note.quarterLength
duration
for chord_note in note.pitches:
= chord_note.ps
pitch = note.volume.realized
volume
score.append([start, duration, pitch, volume, instrument])
else:
= note.offset
start = note.quarterLength
duration = note.pitch.ps
pitch = note.volume.realized
volume
score.append([start, duration, pitch, volume, instrument])
= sorted(score, key=lambda x: (x[0], x[2]))
score return score
= m21.converter.parse("../data_FMP/FMP_C1_F01_Beethoven_FateMotive_Sibelius-Tracks.xml")
xml_data = xml_to_list(xml_data)
xml_list
= pd.DataFrame(xml_list[:9], columns=['Start', 'End', 'Pitch', 'Velocity', 'Instrument'])
df = df.to_html(index=False, float_format='%.2f', max_rows=8)
html ipd.HTML(html)
Start | End | Pitch | Velocity | Instrument |
---|---|---|---|---|
0.50 | 0.50 | 55.00 | 0.71 | Piano (2) |
0.50 | 0.50 | 67.00 | 0.71 | Piano (2) |
1.00 | 0.50 | 55.00 | 0.71 | Piano (2) |
1.00 | 0.50 | 67.00 | 0.71 | Piano (2) |
... | ... | ... | ... | ... |
1.50 | 0.50 | 67.00 | 0.71 | Piano (2) |
2.00 | 2.00 | 63.00 | 0.71 | Piano (2) |
2.50 | 0.50 | 43.00 | 1.00 | Piano (2) |
3.00 | 0.50 | 43.00 | 1.00 | Piano (2) |
=(8, 3), velocity_alpha=True,
visualize_piano_roll(xml_list, figsize='Time (quarter lengths)'); xlabel
= m21.converter.parse("../data_FMP/FMP_C1_F10_Beethoven_Fifth-MM1-21_Sibelius-Orchestra.xml")
xml_data = xml_to_list(xml_data)
xml_list
=(10, 7), velocity_alpha=False,
visualize_piano_roll(xml_list, figsize='gist_rainbow', xlabel='Time (quarter lengths)'); colors
기호 음악 표현법을 사용하는 파이썬 라이브러리
PrettyMIDI
: MIDI 읽기, 컨버팅 등music21
: musicxml파일 다루기pypianoroll
: 피아노롤 비주얼
출처:
https://musicinformationretrieval.com/
https://www.audiolabs-erlangen.de/FMP
\(\leftarrow\) 2.1. 악보 표현